package cn.cdm.service;

import cn.cdm.dao.MiaoshaUserDao;
import cn.cdm.domain.MiaoshaUser;
import cn.cdm.exception.GlobalException;
import cn.cdm.redis.MiaoshaUserKey;
import cn.cdm.redis.RedisService;
import cn.cdm.result.CodeMsg;
import cn.cdm.util.MD5Util;
import cn.cdm.util.UUIDUtil;
import cn.cdm.vo.LoginVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

import static cn.cdm.result.CodeMsg.MOBILE_NOT_EXIST;
import static cn.cdm.result.CodeMsg.PASSWORD_ERROR;

@Slf4j
@Service
public class MiaoshaUserService {
	
	
//	public static final String COOKI_NAME_TOKEN = "token";
	public static final String COOKI_NAME_TOKEN = "loginSessionId";

	@Autowired
	MiaoshaUserDao miaoshaUserDao;
	
	@Autowired
	RedisService redisService;

	/**
	 * 系统启动时，执行顺序如下所示：
	 *   1）MiaoshaUserService#init方法先执行，注入到容器中；
	 *   2）然后cn.cdm.util.ApplicationContextUtils#setApplicationContext() 才执行。
	 */
	@PostConstruct
	public void init() {
		log.info("MiaoshaUserService对象的实例化、初始化");

	}

	/**
	 * 以id+手机号 作为键，从Redis中查询键对应的值
	 * @param id
	 * @return
	 */
	private MiaoshaUser getById(long id) {
		// Redis中的key为 MiaoshaUserKey:id18624072718。手机号作为Redis中的键
		MiaoshaUser user = redisService.get(MiaoshaUserKey.id, "" + id, MiaoshaUser.class);
		if(user != null){
			return user;
		}
		// 当Redis缓存中该用户信息为空，则去查询数据库
		user = miaoshaUserDao.getById(id);
		if(user != null){
			redisService.set(MiaoshaUserKey.id, "" + id, user);
		}
		return user;
	}

	/**
	 * 通过客户端传递过来的token，从Redis缓存中根据该token键，来查找指定的用户对象
	 * @param token
	 * @return
	 */
	public MiaoshaUser getByToken(String token) {
		if (StringUtils.isEmpty(token)) {
			return null;
		}
		MiaoshaUser user = redisService.get(MiaoshaUserKey.token, token, MiaoshaUser.class);
		return user;

	}

	/**
	 * 以sessionId作为键，从Redis中查找值。
	 *
	 * 对象级缓存，也就是缓存秒杀用户对象
	 * 通过token来获取秒杀用户对象。并生成最新的cookie信息，封装在响应信息中，然后返回给客户端
	 * @param response
	 * @param sessionId
	 * @return
	 */
	public MiaoshaUser getByToken(HttpServletResponse response, String sessionId) {
		if(StringUtils.isEmpty(sessionId)) {
			return null;
		}
		// 根据sessionId，来获取指定的用户
		MiaoshaUser user = redisService.get(MiaoshaUserKey.token, sessionId, MiaoshaUser.class);
		// 延长token的有效期。todo 此处只需要刷新Redis中该sessionId的过期时间即可，没必要再次创建新的cookie对象
		if(user != null) {
			addCookie(response, sessionId, user);
		}
		return user;
	}

	/**
	 * 将sessionId（token）和用户对象存入Redis
	 * 将该用户cookie信息存入响应体中
	 * @param response
	 * @param sessionId
	 * @param user
	 */
	private void addCookie(HttpServletResponse response, String sessionId, MiaoshaUser user) {
		// 用Redis缓存服务器单独管理我们的Session用户信息，这就是分布式session
		/*String key = "-" + user.getNickname() + "-" + sessionId;*/
		redisService.set(MiaoshaUserKey.token, sessionId, user);  // Redis中的key为 MiaoshaUserKey:login84e491e5910a4f77ab640c963fba148a
		Cookie cookie = new Cookie(COOKI_NAME_TOKEN, sessionId);
		cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds());
		// 设置 Web网站的根目录
		cookie.setPath("/");
		// 将cookie信息，写入响应对象，返回给客户端
		response.addCookie(cookie);
	}

	/**
	 * 缓存更新问题
	 * @param token
	 * @param id
	 * @param formPass
	 * @return
	 */
	// http://blog.csdn.net/tTU1EvLDeLFq5btqiK/article/details/78693323
	public boolean updatePassword(String token, long id, String formPass) {
		//取user
		MiaoshaUser user = getById(id);
		if(user == null) {
			throw new GlobalException(MOBILE_NOT_EXIST);
		}
		// 更新数据库
		MiaoshaUser toBeUpdate = new MiaoshaUser();
		toBeUpdate.setId(id);
		toBeUpdate.setPassword(MD5Util.formPassToDBPass(formPass, user.getSalt()));
		miaoshaUserDao.update(toBeUpdate);
		// 处理Redis缓存
		redisService.delete(MiaoshaUserKey.id, ""+id);
		user.setPassword(toBeUpdate.getPassword());
//		redisService.set(MiaoshaUserKey.id, ""+id, user);
		// 更新Redis中的token信息
		redisService.set(MiaoshaUserKey.token, token, user);
		return true;
	}


	/**
	 * 登录功能的Service
	 * @param response
	 * @param loginVo
	 * @return
	 */
	public String login(HttpServletResponse response, LoginVo loginVo) {
		if(loginVo == null) {
			// 出现了业务异常，直接往外抛
			throw new GlobalException(CodeMsg.SERVER_ERROR);
		}
		String mobile = loginVo.getMobile();
		String formPass = loginVo.getPassword();

		// 判断手机号是否存在，从Redis或Mysql中取出秒杀用户
		MiaoshaUser user = getById(Long.parseLong(mobile));
		if(user == null) {
			throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
		}

		/*
		// 根据用户输入的手机号，从数据库中查询该用户信息
		MiaoshaUser user = miaoshaUserDao.getById(Long.parseLong(mobile));
		if (user == null) {
			throw new GlobalException(MOBILE_NOT_EXIST);
		}
		*/

		// 若数据库中存在该手机号对应的用户信息，则去验证密码
		String dbPass = user.getPassword();
		// todo 数据库用户表中，每个用户的盐值是相同的，这样用户信息会存在安全问题。
		// 后续可实现用户注册的接口，对每个用户生成随机的盐值，并存入数据库的用户表中。代码如下：
		// 生成盐，默认长度16位，使用了Apache的Shiro安全框架
		// String salt = new SecureRandomNumberGenerator().nextBytes().toString();

		String saltDB = user.getSalt();
		// 利用MD5算法，对表单的密码做第二次MD5加密，计算得到的密文
		String calcPass = MD5Util.formPassToDBPass(formPass, saltDB);
		// 将计算出来的密码与数据库的密钥做比较
		if(!calcPass.equals(dbPass)) {
			throw new GlobalException(PASSWORD_ERROR);
		}
		// 手机号、密码都验证通过后，生成唯一标识符sessionId，存入cookie对象并返回给客户端
		String sessionId = UUIDUtil.uuid();
		addCookie(response, sessionId, user);

		return sessionId;
	}

}
































